﻿using System;
using System.Xml.Linq;
using SchemaGen.Xsd;

namespace SchemaGen.Attributes
{
    /// <summary>
    /// WARNING: Only works on Public class members
    /// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
    public abstract class XmlEntityBaseAttribute : Attribute
    {
        // Used internally to initialize XML heirarchy
        internal bool IsComplexElement;
        internal string ParentFullName;

        public string Name { get; private set; }
        public string[] Segments { get; private set; }
        public int Depth { get { return Segments.Length; } }
        public string LocalName { get { return Segments[Segments.Length - 1]; } }
        public string ParentName { get { return Segments.Length > 1 ? Segments[Segments.Length - 2] : null; } }

        private XName _TypeName;
        public XName TypeName 
        { 
            get { return _TypeName; }
            protected set
            {
                if (_TypeName != null) throw
                    new SchemaException(string.Format("Type was set more than once. It was set to {0} and then tried to be set to {1}.", _TypeName.LocalName, value.LocalName));
                _TypeName = value;
            }
        }
        // public virtual XName TypeName { get { return SNames.SimpleTypeLookup[Type]; } }

        public string Documentation { get; set; }
        public object DefaultValue { get; set; }
        public bool IsFixed { get; set; }
        
        /// <summary>
        /// This value is ignored unless the element is part of an XmlComplexType.
        /// If this element is part of a SequenceIndicator.Sequence then this value is used to determine the order.
        /// If it is not included, the elements will be ordered as they appear in the document.
        /// </summary>
        public int SequenceOrder { get; set; }

        XsdSimpleType _Type;
        /// <summary>
        /// Set this or ClrType
        /// </summary>
        public XsdSimpleType Type
        {
            get { return _Type; }
            set
            {
                TypeName = SNames.SimpleTypeLookup[value];
                _Type = value;
            }
        }

        private Type _ClrType;
        /// <summary>
        /// Set this instead of Type if you want to use typeof(int) or something. Works with most value types.
        /// </summary>
        public Type ClrType
        {
            get { return _ClrType; }
            set
            {
                XName typeName;

                if (typeof(Enum).IsAssignableFrom(value)) // Is enumeration
                {
                    TypeName = value.Name;
                }
                else if (SNames.ClrTypeLookup.TryGetValue(value, out typeName)) // Has xml equivalent
                {
                    TypeName = typeName;
                }
                else // No xml equivalent
                {
                    throw new SchemaException(string.Format("Clr type {0} does not have an XML equivalent", value.Name));
                }
                    
                _ClrType = value;
            }
        }

        public XmlEntityBaseAttribute(params string[] nameSegments)
            : this(string.Join(new string(Globals.SEGMENT_DELIMITER_CHAR, 1), nameSegments))
        { }

        public XmlEntityBaseAttribute(string name)
        {
            Name = name;
            _Type = XsdSimpleType.NULL;
            Segments = name.Split(Globals.SEGMENT_DELIMITER, StringSplitOptions.RemoveEmptyEntries);

            foreach (var segment in Segments)
            {
                if (!Globals.XML_NAME_REGEX.IsMatch(segment)) throw new ArgumentException(
                        string.Format("Argument must be a series of valid XML names separated by '{0}' characters. The name \"{1}\" is invalid.", Globals.SEGMENT_DELIMITER_CHAR, segment),
                        "name");
            }

            // Internal use only
            ParentFullName = Segments.Length == 1 ? null : name.Substring(0, Name.LastIndexOf(Globals.SEGMENT_DELIMITER_CHAR));
        }

        public override string ToString()
        {
            return Name;
        }
    }
}
